#include "rand.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>

#include "util/mmgr.h"		    // Needs to come in early

using namespace num;

RAND32	*g_pRAND = NULL;

void RAND32::seedMT(unsigned long seed)
{
    register unsigned long x = (seed | 1U) & 0xFFFFFFFFU, *s = state;
    register int    j;

	// fugly chunk of code.
    for(left=0, *s++=x, j=_RAND_N; --j; *s++ = (x*=69069U) & 0xFFFFFFFFU)
    {
    	
    }
}

unsigned long RAND32::reloadMT(void)
{
    register unsigned long *p0=state, *p2=state+2, *pM=state+_RAND_M, s0, s1;
    register int    j;

    if(left < -1)
        seedMT(4357U);

    left=_RAND_N-1, next=state+1;

    for(s0=state[0], s1=state[1], j=_RAND_N-_RAND_M+1; --j; s0=s1, s1=*p2++)
        *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? _RAND_K : 0U);

    for(pM=state, j=_RAND_M; --j; s0=s1, s1=*p2++)
        *p0++ = *pM++ ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? _RAND_K : 0U);

    s1=state[0], *p0 = *pM ^ (mixBits(s0, s1) >> 1) ^ (loBit(s1) ? _RAND_K : 0U);
    s1 ^= (s1 >> 11);
    s1 ^= (s1 <<  7) & 0x9D2C5680U;
    s1 ^= (s1 << 15) & 0xEFC60000U;
    return(s1 ^ (s1 >> 18));
}

unsigned long RAND32::randomMT(void)
{
    unsigned long y;

    if(--left < 0)
        return(reloadMT());

    y  = *next++;
    y ^= (y >> 11);
    y ^= (y <<  7) & 0x9D2C5680U;
    y ^= (y << 15) & 0xEFC60000U;
    return(y ^ (y >> 18));
}

RAND32&  RAND32::Instance()
{
    static RAND32 Inst;
    return Inst;
}

RAND32::RAND32()
{
    //time_t seed = time(NULL);
    //seed |= 0x01;
    seedMT(static_cast<unsigned long>(time(NULL)));
    //left = -1;
}

RAND32::~RAND32()
{
}


int RAND32::RandInt()
{
    return int(randomMT() % UINT_MAX);
}

int RAND32::RandInt(int min, int max)
{
    int temp;

    if (min > max)
    {
        temp = min;
        min = max;
        max = temp;
    }
    else if (min == max) 
    {
        return min;
    }

    int range = max - min + 1;
    //if (range == 0) range = 1;
    return int(min + randomMT() % range);
}

/// range [0, 1]
float RAND32::RandFloat()
{
    // code optimized. Never change it without awareness
    return randomMT() * (1 / (0x10000 * static_cast<float>(0x10000)));
}

float RAND32::RandFloat(float min, float max)
{
    float temp;

    if (min > max)
    {
        temp = min;
        min = max;
        max = temp;
    }
    else if (min == max)
    {
        return min;
    }
    
    float range = max - min;
    temp = RandFloat() * range + min;
    // boundary check, just in case
    if (temp < min)
        temp = min;
    else if (temp > max)
        temp = max;
    return temp;
}

double RAND32::RandDouble()
{
    // code optimized. Never change it without awareness
    return randomMT() * (1 / (0x10000 * static_cast<double>(0x10000)));
}

///\note the random variable may not abide with uniform distribution
double RAND32::RandDouble(double min, double max)
{
    double temp;

    if (min > max)
    {
        temp = min;
        min = max;
        max = temp;
    }
    else if (min == max)
    {
        return min;
    }
    
    double range = max - min;
    temp = RandDouble() * range + min;
    // boundary check, just in case
    if (temp < min)
        temp = min;
    else if (temp > max)
        temp = max;
    return temp;
}


math::Vec2 RAND32::RandVec2()
{
    return math::Vec2(RandFloat(), RandFloat());
}

math::Vec2 RAND32::RandVec2(float min, float max)
{
    return math::Vec2(RandFloat(min, max), RandFloat(min, max));
}

math::Vec2 RAND32::RandVec2(const math::Vec2& min, const math::Vec2& max)
{
    return math::Vec2(RandFloat(min.x, max.x), RandFloat(min.y, max.y));
}

math::Vec3 RAND32::RandVec3()
{
    return math::Vec3(RandFloat(), RandFloat(), RandFloat());
}

math::Vec3 RAND32::RandVec3(float min, float max)
{
    return math::Vec3(RandFloat(min, max), RandFloat(min, max), RandFloat(min, max));
}

math::Vec3 RAND32::RandVec3(const math::Vec3& min, const math::Vec3& max)
{
    return math::Vec3(RandFloat(min.x, max.x), RandFloat(min.y, max.y), RandFloat(min.z, max.z));
}
